package serveur;

import java.io.IOException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Timer;


public class Partie implements Runnable{
	private int nbjoueurs;
	private int indice;
	protected ArrayList<Joueur> joueurs;
	private boolean full;
	private boolean fin;
	private boolean exitTour;
	private String winner;
	private int ordre;
	private boolean enAttente;

	Partie(int indice){
		nbjoueurs = 0;
		joueurs = new ArrayList<Joueur>();
		this.indice = indice;
		enAttente = true;
		full = false;
		fin = false;
		exitTour = false;
		ordre = 0;
	}

	@Override
	public void run() {
		this.setEnAttente(false);
		startPlacement();
		startPartie();
		gererFinPartie();
		AccepterConnexion.erasePartie(this);
	}


	//Normalement le synchronized n'est pas necessaire
	//car l'ajout des joueurs se fait les uns apres les autres
	//en "file d'attente"

	/**
	 * Cette fonction permet l'ajout du joueur j à la partie et permet
	 * le lancement du timer.
	 * @param j
	 */
	synchronized void ajoutJoueur(Joueur j){
		if(nbjoueurs <=4){
			joueurs.add(j);
			nbjoueurs++;
			if(nbjoueurs == 4){
				this.full= true;
			}
			else if(nbjoueurs == 2){
				//Lancement du timer des la connexion du 2e joueur		
				Timer t = new Timer();
				t.schedule(new TimerPartie(this), 5000);

			}
		}
	}


	void afficheListeJoueurs(){
		int i=0;
		for(Joueur j : joueurs){
			if (j != null)
				System.out.println(i+" - nom :"+j.getNom());
		}
	}

	/**
	 * Affecte l'ordre de passage du joueur
	 * @param joueur
	 */
	synchronized void placementIsOk(Joueur joueur){
		joueur.setOrdrePsg(ordre);
		ordre++;
	}

	/**
	 * Lance le thread de placement des navires pour chaque jour 
	 */
	protected void startPlacement(){

		//Construction du message players à envoyer
		String msg = Commande.PLAYERS;
		for(int i=0; i<nbjoueurs; i++){
			msg += joueurs.get(i).getNom()+"/";
		}
		msg+="\n";

		//Envoi du message de debut
		for(Joueur j : joueurs){
			try{
				j.sendMessage(msg);
			} catch(SocketException se){
				System.err.println("Un joueur s'est déconnecté.");
				se.printStackTrace();
				j.setPerdant(true);

			} catch (IOException e) {
				System.err.println("Pb envoi msg debut partie");
				e.printStackTrace();
			} 
		}

		//Lancement des threads de placements des navires
		for(Joueur j : joueurs){
			j.initPlacerNavires();
			j.getPlacerNavires().start(); 
		}

		//On attend que tous les joueurs aient places leurs bateaux :)
		for(int i=0; i<nbjoueurs; i++){
			try{
				joueurs.get(i).getPlacerNavires().join();
			}catch (InterruptedException e) {
				System.err.println("Un joueur s'est déconnecté.");
				e.printStackTrace();
			}
		}

		if(joueurs.size() == 0){
			setFin(true);
		}
		else if (joueurs.size() == 1){
			winner = joueurs.get(0).getNom();
		}
	}

	protected void startPartie() {
		int tour=0;

		for(Joueur j : joueurs){
			try {
				if(!j.isPerdant()){
					j.getSocket().setSoTimeout(30000);
				}
			} catch (SocketException e) {
				System.err.println("ERREUR SETTIMEOUT");
				e.printStackTrace();
			}
		}


		while(!fin){	
			//TODO Gestion du jeu drone & cie
			tour++;
			System.out.println("\n-------------------- Debut du tour "+
					tour+" ---------------------\n");

			//Tri des joueurs selon ordre de passage
			Collections.sort(joueurs);				

			for(Joueur j : joueurs){
				System.out.println("Ordre de passage : "+j.getOrdrePsg() +
						" Joueur : "+j.getNom()+"\n");
			}

			String msg = "";
			String ans = "";

			for(Joueur j : joueurs){
				if(exitTour){
					break;
				}
				try{

					if(!j.isPerdant()){
						msg = Commande.YOURTURN;
						msg += j.getDroneX()+"/";
						char yChar = (char)(j.getDroneY()+65);
						msg += yChar+"/";
						msg += j.getNbPasDrone()+"/\n";
						j.sendMessage(msg);
						Thread.sleep(100);

						ans = j.receiveMessage();

						if(ans == null){
							System.err.println("Joueur deconnecté");
							j.setPerdant(true);
							isThereAnyLoosers();
						}
						else if(!ans.startsWith(Commande.ACTION)){
							//commande invalide : on passe le tour du joueur
							continue;
						}
						else if(ans.startsWith(Commande.ACTION)) {
							String [] tabString = ans.split("/");

							if(tabString.length -1 != j.getNbPasDrone()){
								continue;
							}else{

								String [] listActions = new String[tabString.length-1];
								System.arraycopy(tabString,1,listActions,0,tabString.length-1);

								gererDrone(listActions, j);

								//on regarde a chaque tour si on a un nv perdant
								isThereAnyLoosers();
							}
						}
					}
					// si j a deja perdu alors on ne fait rien
				}catch (SocketTimeoutException ste){
					System.out.println("Timeout joueur "+j.getNom());
					ste.printStackTrace();

					continue;
					/*
					try {
						j.setPerdant(true);
						j.close();
						isThereAnyLoosers();
						int nbPerdants=0;
						
						for(Joueur player : joueurs){
							if(player.isPerdant()){
								nbPerdants++;
							}
						}
						
						if(nbPerdants == nbjoueurs-1){
							setExitTour(true);
							setFin(true);
						}

					} catch (IOException e) {
						System.err.println("Erreur close joueur");
						e.printStackTrace();
					}*/

				}catch(IOException ioe){
					System.err.println("PB pendant boucle de jeu");
					ioe.printStackTrace();
				}catch(InterruptedException ie){
					System.err.println("Interruption sleep?");
					ie.printStackTrace();
				}

				if(exitTour){
					break;
				}
			}
		}
	}

	protected void gererFinPartie(){	

		//Signale le vainqueur
		for(Joueur j : joueurs){

			try {
				if(winner != null){
					String endMsg = Commande.AWINNERIS+winner+"/\n";
					j.sendMessage(endMsg);
				}
			}catch (SocketTimeoutException ste){
				System.out.println("Deconnexion du joueur "+j.getNom());
				joueurs.remove(j.getOrdrePsg());
				ste.printStackTrace();

			}catch (IOException e) {
				System.err.println("Probleme fin de partie : End Message");
				e.printStackTrace();
			} 
		}

		//Attente de réponse pour rejouer ou pas
		String replay = "";

		for(Joueur j : joueurs){
			try {
				replay = j.receiveMessage();

				if (Commande.PLAYAGAIN.equals(replay)){
					j.getSocket().setSoTimeout(100000);
					System.out.println("-------------->JE VEUX REJOUER");

					//on prepare le joueur à la prochaine partie
					j.resetOrdre();

					AccepterConnexion.addPlayerPartie(j);

				}else{
					System.out.println("-------------->JE NE VEUX RIEN");
					j.close();
				}

			}catch (SocketTimeoutException ste){
				System.out.println("Deconnexion du joueur "+j.getNom());
				try {
					j.close();
				} catch (IOException e) {
					System.err.println("Erreur fermeture socket client");
					e.printStackTrace();
				}
				//joueurs.remove(j.getOrdrePsg());
				ste.printStackTrace();

			}catch (IOException e) {
				System.err.println("Probleme fin de partie : Replay");
				e.printStackTrace();
			} 
		}

	}

	/**
	 * Cette fonction cherche les perdants.
	 * @throws IOException
	 */
	protected void isThereAnyLoosers() throws IOException{
		int nbPerdants = 0;
		for(Joueur j : joueurs){
			if(j.aPerdu()){
				gererPerdant(j);
				nbPerdants++;
			}	
		}
		if(nbPerdants == nbjoueurs -1){
			gererGagnant();

		}else if (nbPerdants == nbjoueurs){
			String drawgame = Commande.DRAWGAME+"\n";
			for(Joueur j :joueurs){
				j.sendMessage(drawgame);
			}
			setFin(true);
			setExitTour(true);
		}
	}

	/**
	 * Cette fonction signale le perdant j aux autres joueurs.
	 * @param j
	 * @throws IOException
	 */
	protected void gererPerdant(Joueur j) throws IOException{
		//reset de tous les navires du perdant
		j.resetNavires();

		j.setPerdant(true);

		//signalement du perdant
		String msgDuPerdant = Commande.DEATH+j.getNom()+"/\n";
		for(Joueur eachj : joueurs){
			eachj.sendMessage(msgDuPerdant);
		}
	}

	/** gererGagnant permet de signaler la fin de la boucle de jeu
	 *  et de rechercher le gagnant.
	 */
	protected void gererGagnant(){
		setExitTour(true);
		setFin(true);

		//Il n'y a plus qu'un joueur non perdant : on le cherche
		for(Joueur j : joueurs){
			if(!j.isPerdant()){
				//on l'a trouve : on affecte son nom au nom du gagnant
				winner = j.getNom();	
			}
		}
	}


	/**
	 * Cette fonction permet de differencier un deplacement d'une elimination
	 * et appelle les fonctions de gestion correspondante en consequence. 
	 * @param actions
	 * @param j
	 * @throws IOException
	 */
	protected void gererDrone(String[] actions, Joueur j) throws IOException{
		boolean laserIsUsed = false;

		for(int i=0; i<actions.length; i++){

			if("E".equals(actions[i])){
				if(!laserIsUsed){
					activerLaser(j);
				}else{
					//Le laser a deja ete utilise -> on passe l'action suivante
					continue;
				}
			}
			else if(
					("U".equals(actions[i])) ||
					("D".equals(actions[i])) ||
					("L".equals(actions[i])) ||
					("R".equals(actions[i])) ){
				if(!deplaceDroneJoueur(j, actions[i])){
					//mauvais placement : on passe a l'action suivante
					continue;
				}
			}else{
				//action non reconnu : on passe a l'action suivante
				continue;
			}	
		}
	}

	/**
	 * deplaceDroneJoueur deplace le drone du joueur j d'une case vers Direction
	 * @param j
	 * @param direction
	 * @return
	 */
	protected boolean deplaceDroneJoueur(Joueur j, String direction){
		int droneX = j.getDroneX();
		int droneY = j.getDroneY();

		switch(direction){

		case "U" :
			if(droneY+1 > 15){
				return false;
			} else{
				j.setDroneY(droneY+1);
			}
			break;

		case "D" :
			if(droneY-1 < 0){
				return false;
			} else{
				j.setDroneY(droneY-1);
			}
			break;

		case "L" :
			if(droneX-1 <0){
				return false;
			} else{
				j.setDroneX(droneX-1);
			}
			break;

		case "R" :
			if(droneX+1 >15){
				return false;
			} else{
				j.setDroneX(droneX+1);
			}
			break;

		default:
			return false;
		}

		return true;
	}

	/**
	 * Cette fonction est appelee lors d'une elimination et permet de signaler 
	 * si le laser du joueur j a touche les autres joueurs ou s'il s'est touche
	 * lui-meme.
	 * @param j
	 * @return
	 * @throws IOException
	 */
	protected boolean aToucheAuMoinsUnJoueur(Joueur j) throws IOException{
		boolean touchOthers = false;
		boolean autotouch = false;

		int droneX = j.getDroneX();
		char charDroneY = (char)(j.getDroneY()+65);
		int droneY = j.getDroneY();

		String touch = Commande.TOUCHE+droneX+"/"+charDroneY+"/\n";
		String ouch = Commande.OUCH+droneX+"/"+charDroneY+"/\n";

		for(Joueur autreJ : joueurs){
			if(!j.equals(autreJ)){
				if(autreJ.estTouche(droneX, droneY)){
					autreJ.sendMessage(ouch);
					autreJ.setNbPasDrone(autreJ.getNbPasDrone()+1);
					touchOthers = true;
				}				
			}else{
				if(j.estTouche(droneX, droneY)){
					autotouch = true;
					j.setNbPasDrone(autreJ.getNbPasDrone()+1);
				}
			}
		}
		//Pour n'envoyer qu'une fois touche
		if(touchOthers){
			j.sendMessage(touch);
		}

		//Pour envoyer ouch apres touch
		if(autotouch){
			j.sendMessage(ouch);
		}

		return touchOthers;
	}

	/**
	 * Cette fonction permet de signaler au joueur qu'il a loupe les 
	 * navires des autres joueurs et les siens.
	 * @param j
	 * @throws IOException
	 */
	protected void activerLaser(Joueur j) throws IOException{
		if(!aToucheAuMoinsUnJoueur(j)){
			String miss = Commande.MISS;
			miss += j.getDroneX()+"/";
			miss += (char)(j.getDroneY()+65)+"/\n";
			j.sendMessage(miss);
		}
	}


	public int getNbjoueurs() {
		return nbjoueurs;
	}

	protected void setNbjoueurs(int nbjoueurs) {
		this.nbjoueurs = nbjoueurs;
	}

	public int getIndice() {
		return indice;
	}

	protected void setIndice(int indice) {
		this.indice = indice;
	}

	public ArrayList<Joueur> getJoueurs() {
		return joueurs;
	}

	public boolean isFull() {
		return full;
	}

	/**
	 * @param fin the fin to set
	 */
	private void setFin(boolean fin) {
		this.fin = fin;
	}

	/**
	 * @return the enAttente
	 */
	protected boolean isEnAttente() {
		return enAttente;
	}

	/**
	 * @param enAttente the enAttente to set
	 */
	protected void setEnAttente(boolean enAttente) {
		this.enAttente = enAttente;
	}

	/**
	 * @return the exitTour
	 */
	protected boolean isExitTour() {
		return exitTour;
	}

	/**
	 * @param exitTour the exitTour to set
	 */
	protected void setExitTour(boolean exitTour) {
		this.exitTour = exitTour;
	}


}